// Package similarity provides functions for calculating the cosine similarity between two strings.
/*
Copyright © 2024 weidongkl <weidongkx@gmail.com>
*/

package cosine

import (
	"math"
)

type Cosine struct {
}

func New() *Cosine {
	return &Cosine{}
}

func (c *Cosine) Calculate(s1, s2 string) (float64, error) {
	return CosineSimilarity(s1, s2)
}
func (c *Cosine) Name() string {
	return "cosine"
}

// getCharFrequencies calculates and returns the frequency map of each character in the string s.
// The key of the map is the character, and the value is the occurrence count of the character.
func getCharFrequencies(s string) map[rune]int {
	freq := make(map[rune]int)
	for _, char := range s {
		freq[char]++
	}
	return freq
}

// CosineSimilarity calculates the cosine similarity between two strings s1 and s2.
// Returns the similarity value between 0 and 1, and an error value if an error occurs.
// If s1 and s2 are identical, the similarity is 1, and if they have no common characters, the similarity is 0.
func CosineSimilarity(s1, s2 string) (float64, error) {
	if s1 == s2 {
		return 1, nil
	}
	freq1 := getCharFrequencies(s1)
	freq2 := getCharFrequencies(s2)
	dotProduct := 0.0
	for char, count1 := range freq1 {
		if count2, exists := freq2[char]; exists {
			dotProduct += float64(count1 * count2)
		}
	}
	magnitude1 := 0.0
	for _, count := range freq1 {
		magnitude1 += float64(count * count)
	}
	magnitude1 = math.Sqrt(magnitude1)
	magnitude2 := 0.0
	for _, count := range freq2 {
		magnitude2 += float64(count * count)
	}
	magnitude2 = math.Sqrt(magnitude2)
	if magnitude1 == 0 || magnitude2 == 0 {
		return 0, nil
	}
	return dotProduct / (magnitude1 * magnitude2), nil
}
